//
// Copyright 2001-2004 Stottler Henke Associates, Inc.
// All Rights Reserved.
//////////////////////////////////////////////////////////////////////

package com.stottlerhenke.simbionic.engine.file;

import java.net.*;
import java.io.*;

import com.stottlerhenke.simbionic.api.SB_Vector;
import com.stottlerhenke.simbionic.common.*;


/**
 * Encapsulates the ability to read tokenized data from an ASCII text file,
 * using buffering to speed up file access.
 */
public class SB_TextFileReader extends SB_FileReader implements SB_StreamReader
{
	private SB_Tokenizer _tokenizer = new SB_Tokenizer();
	public static final String[] TAGS =
	{
			"File-Version",
			"Sim-Format",
			"Parameters",
			"Polys",
			"Variables",
			"Nodes",
			"Bindings",
			"Trans-Indices",
			"Transitions",
			"Conditions",
			"Hierarchies",
			"Actions",
			"Predicates",
			"Globals",
			"Behaviors",
			"Sim-File",
			"Classes",
	};

	/**
	 * Constructor
	 * @param fileURL the URL of the file to read from
	 */
	public SB_TextFileReader(URL fileURL)
	{
		super(fileURL);
	}

	/**
	 * Constructor
	 * @param stream an open input stream to read from
	 */
	public SB_TextFileReader(InputStream stream)
	{
		super(stream);
	}

	/**
	 * Reads the next integer token from the stream.  Whitespace is consumed.
	 * @return the next integer token from the stream
	 * @throws SB_FileException if the next token is not an integer
	 */
	public int readInt() throws SB_FileException
	{
		String token = _tokenizer.getNextToken();
		int theInt;
		try {
			theInt = Integer.parseInt(token);
		} catch (NumberFormatException e) {
			throw new SB_FileException("Expecting integer in specification, got \"" + token + "\" instead.");
		}
		return theInt;
	}
	
	/**
	 * Reads the next float token from the stream.  Whitespace is consumed.
	 * @return the next float token from the stream
	 * @throws SB_FileException if the next token is not a float
	 */
	public float readFloat() throws SB_FileException
	{
		String token = _tokenizer.getNextToken();
		float theFloat;
		try {
			theFloat = Float.parseFloat(token);
		} catch (NumberFormatException e) {
			throw new SB_FileException("Expecting float in specification.");
		}
		return theFloat;
	}
	
	/**
	 * Reads the next boolean token from the stream.  Whitespace is consumed.
	 * @return the next boolean token from the stream
	 * @throws SB_FileException if the next token is not a boolean
	 */
	public boolean readBool() throws SB_FileException
	{
		String token = _tokenizer.getNextToken();
		if ((token.compareToIgnoreCase("true") == 0) || (token.compareTo("1") == 0))
			return true;
		return false;
	}
	
	/**
	 * Reads the next vector from the stream.  Whitespace is consumed.
	 * @return the read vector
	 * @throws SB_FileException if the next sequence is not a vector
	 */
	public SB_Vector readVector() throws SB_FileException
	{
		SB_Vector vector = new SB_Vector();
	  
	  readChar('[',true,true);
	
		// need to tokenize on commas and brackets for vectors
		String normalDelims = _tokenizer.setDelimiters(" ,]\r\n\t");
		
	  vector.x = readFloat();
	  readChar(',',true,false);
	  
	  vector.y = readFloat();
	  readChar(',',true,false);
	  
	  vector.z = readFloat();
	  
	  // restore normal tokenizer delimiters
	  _tokenizer.setDelimiters(normalDelims);
	  
	  return vector;
	}
	
	/**
	 * Reads a char from the stream.  Only the specified character will be eaten, 
	 * if it exists.  If mustBePresent is true, an exception will be thrown if 
	 * the specified character is not present.
	 * @param charToRead an optional restriction on the character to read
	 * @param eatWS if true, whitespace will be consumed before reading the char
	 * @param mustBePresent if true and the next character does not match charToRead,
	 *						throws an exception
	 * @throws SB_FileException if the specified character isn't present
	 */
	public char readChar(char charToRead,boolean eatWS,boolean mustBePresent) throws SB_FileException
	{
		if (eatWS)
		{
			_tokenizer.trim();
		}
		
		// peek ahead at the next character
		char theChar = _tokenizer.getNextChar(false);
		
		if (theChar == charToRead)
		{
			// eat the character
			theChar = _tokenizer.getNextChar(true);
		}
		else if (mustBePresent)
		{
			throw new SB_FileException( "Expecting '" + charToRead + "' in specification stream.");
		}
		
		return theChar;		
	}
	
	/**
	 * @return a Pascal-style string from the stream
	 * @throws SB_FileException on failure
	 */
	public String readPString() throws SB_FileException
	{
		return _tokenizer.getNextDesignatedToken();
	}

	/**
	 * Verifies that the specified section tag is the next item in the 
	 * data stream.
	 * @param tagId the ID of the tag to check
	 * @throws SB_FileException if the specified tag is not present
	 */
	public void readSectionTag(int tagId) throws SB_FileException
	{
		String elem = null;
	 	
	  // get the section tag
    elem = _tokenizer.getNextToken();
    if(!elem.equals( TAGS[tagId] ))
    {
      throw new SB_FileException("Missing tag '" + TAGS[tagId] + "' in behavior file, got '" + elem + "' instead.");
    }
	}
	
	/**
	 * Prepares the file for reading.
	 */
	public void prepare() throws SB_FileException
	{ 
        try
        {
        	// create a string buffer to store the entire file in
        	StringBuffer fileStringBuff = new StringBuffer();
        	
        	BufferedInputStream buffer;
        	if(_fileURL != null)
        		buffer = new BufferedInputStream(_fileURL.openStream());
        	else
        		buffer = new BufferedInputStream(_stream);
        	
        	BufferedReader in = new BufferedReader(new InputStreamReader(buffer));
        	
        	int size = 256;
        	char[] line = new char[size];
        	int i = in.read(line, 0, size);
        	while ( i > -1)
        	{
        		fileStringBuff.append(line, 0, i);
        		i = in.read(line, 0, size);
        	}
        	in.close();
        	_tokenizer.setString(fileStringBuff.toString());
        }
        catch (IOException e) 
        {
        	throw new SB_FileException("Cannot prepare file" + _fileURL.getPath() + " for reading because of: " + e.toString());
        }
        
/* This is more effecient but does not work over http connections
            if(buffer != null)
            {
                byte[] data = new byte[buffer.available()];
                in.read(buffer);
                _tokenizer.setString(new String(data));
            }
*/
	}

	/**
	 * Closes the file.
	 */
	public void close()
	{
		// file is already closed, but free the string buffer to save memory
		_tokenizer.setString("");
	}
	
	public SB_Tokenizer getTokenizer() {return _tokenizer;}

}
